﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Concurrent;
using System.IO;

namespace Fast.Extensions.RabbitMQ
{
    /// <summary>
    /// 连接池
    /// </summary>
    public class ConnectionPool : IConnectionPool, ISingletonDependency
    {
        protected FastRabbitMqOptions Options { get; }
        protected ConcurrentDictionary<string, Lazy<IPersistentConnection>> Connections { get; }

        private readonly ILoggerFactory _loggerFactory;

        private readonly ILogger<ConnectionPool> _logger;

        private bool _isDisposed;

        public ConnectionPool(IOptions<FastRabbitMqOptions> options, ILoggerFactory loggerFactory)
        {
            Options = options?.Value ?? throw new ArgumentNullException(nameof(options));
            Connections = new ConcurrentDictionary<string, Lazy<IPersistentConnection>>();
            _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
            _logger = loggerFactory.CreateLogger<ConnectionPool>();
        }

        /// <summary>
        /// 获取持久连接
        /// </summary>
        /// <param name="connectionName"></param>
        /// <returns></returns>
        public virtual IPersistentConnection Get(string connectionName = null)
        {
            connectionName ??= RabbitMqConnections.DefaultConnectionName;

            try
            {
                var lazyConnection = Connections.GetOrAdd(
                    connectionName, _ => new Lazy<IPersistentConnection>(() =>
                    {
                        var connectionFactory = Options.Connections.GetOrDefault(connectionName);

                        return new PersistentConnection(connectionFactory, _loggerFactory,
                            Options.ConnectionFailRetryCount);
                    })
                );

                return lazyConnection.Value;
            }
            catch (Exception)
            {
                Connections.TryRemove(connectionName, out _);
                throw;
            }
        }

        public void Dispose()
        {
            if (_isDisposed) return;

            _isDisposed = true;

            foreach (var connection in Connections.Values)
            {
                try
                {
                    connection.Value.Dispose();
                }
                catch (IOException ex)
                {
                    _logger.LogCritical(ex.ToString());
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex.ToString());
                }
            }

            Connections.Clear();
        }

        #region Private Methods

        #endregion
    }
}